// wgal.cpp
#define _WIN32_WINNT 0x0500

#include "wgal/internal.h"
#include <wgal.h>

// Function declaration
LRESULT CALLBACK wgalWndProc(HWND, UINT, WPARAM, LPARAM);
DWORD GetWindowStyle(WGALBool);
DWORD GetExWindowStyle(WGALBool);
void SetCloseButtonState(WGALInt32);

// Globals
static HINSTANCE hDllInst = NULL; // DLL instance handle
HWND hWnd = NULL;
static LPCWSTR lpszWndClass = L"WGALGameWindow"; // Window class name
static int main_ret = EXIT_SUCCESS; // return value
WGALBool wgal_init = FALSE; // initialized
static WGALBool wgal_exit = FALSE; // exit flag
LPDIRECT3D9 d3d = NULL;

// callbacks
static WGALUpdateProc	update_proc = NULL;
static WGALDrawProc		draw_proc = NULL;
static WGALCommandProc	minimize_proc = NULL;
static WGALCommandProc	maximize_proc = NULL;
static WGALCommandProc	close_proc = NULL;
static WGALCommandProc	command_proc = NULL;

// options
static WGALFloat interval;				// loop interval
static WGALBool wgal_window = FALSE;	// windowed
static WGALBool loop = FALSE;			// looping
static WGALBool show_cursor = FALSE;	// show cursor
static WGALBool auto_present = TRUE;	// auto-present
static WGALInt32 btn_minimize = WGAL_DEFAULT;
static WGALInt32 btn_maximize = WGAL_DEFAULT;
static WGALInt32 btn_close = WGAL_DEFAULT;
static WGALBool device_lost = FALSE;

#ifdef _MANAGED
#pragma managed(push, off)
#endif

// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		hDllInst = hModule;
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
    return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif

// initializes WGAL
WGALAPI WGALBool wgalInit()
{
	if(wgal_init)
		return FALSE;

	d3d = Direct3DCreate9(D3D_SDK_VERSION);

	if(!d3d)
		return FALSE;

	// Retrieve instance handle
	HINSTANCE hInst = GetModuleHandleW(NULL);

	// Prepare window class
	WNDCLASSEXW wndcls;

	// Memory cleanup
	ZeroMemory(&wndcls, sizeof(WNDCLASSEX));
	wndcls.cbSize = sizeof(WNDCLASSEX);			// size of WNDCLASSEX
	wndcls.style =
		CS_GLOBALCLASS |
		CS_HREDRAW | 
		CS_VREDRAW;								// window style
	wndcls.lpfnWndProc = (WNDPROC)&wgalWndProc;	// window procedure
	wndcls.cbClsExtra = 0;						// number of extra bytes
	wndcls.cbWndExtra = 0;						// number of extra bytes
	wndcls.hInstance = hDllInst;				// instance handle of DLL
	wndcls.hIcon = NULL;						// icon of window
	wndcls.hIconSm = NULL;						// small icon of window
	wndcls.hCursor = 
		LoadCursor(NULL, IDC_ARROW);			// cursor of window
	wndcls.hbrBackground = NULL;				// background of window
	wndcls.lpszClassName = lpszWndClass;		// class name of window class
	wndcls.lpszMenuName = NULL;					// menu of window

	// Register window class
	RegisterClassExW(&wndcls);

	wgal_init = TRUE;

	return TRUE;
}

// sets up a WGAL instance
WGALAPI WGALBool wgalSetupEx(WGALStartupInfoExPtr wsi)
{	
	CHECK_INITIALIZED(wgal_init);

	if(hWnd)
		return FALSE;

	// check prerequisites
	if(!wsi)
		return FALSE;

	/* check parameters */
	if(!wsi->draw_proc || !wsi->update_proc)
		return FALSE;
	
	wgal_window = !wsi->fullscreen;

	// set procedures
	update_proc = wsi->update_proc;
	draw_proc = wsi->draw_proc;
	close_proc = wsi->on_close;
	minimize_proc = wsi->on_minimize;
	maximize_proc = wsi->on_maximize;
	command_proc = wsi->on_command;

	btn_minimize = wsi->btn_minimize;
	btn_maximize = wsi->btn_maximize;

	// extended window style
	DWORD dwExStyle = GetExWindowStyle(wsi->fullscreen);
	// window style
	DWORD dwStyle = GetWindowStyle(wsi->fullscreen);

	BOOL bMenu = FALSE; // has menu

	RECT rWndArea;
	rWndArea.left = 0;
	rWndArea.top = 0;
	rWndArea.right = wsi->width;
	rWndArea.bottom = wsi->height;

	if(wgal_window)
		AdjustWindowRectEx(&rWndArea, dwStyle, bMenu, dwExStyle);

	// create window
	hWnd = CreateWindowExW(dwExStyle,
		lpszWndClass,
		wsi->title,
		dwStyle,
		CW_USEDEFAULT, CW_USEDEFAULT,
		rWndArea.right - rWndArea.left,
		rWndArea.bottom - rWndArea.top,
		NULL,
		NULL,
		GetModuleHandle(NULL),
		NULL
		);

	if(!hWnd)
		return FALSE;

	SetCloseButtonState(wsi->btn_close);

	if(!wgalGraphicsInit(hWnd))
	{		
		DestroyWindow(hWnd);

		/*
		// clean the message queue until WM_QUIT is popped out
		MSG msg;
		
		while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message == WM_QUIT)
				break;
		}
		//*/

		return FALSE;
	}

	if(wsi->icon_id != ID_NONE)
	{
		SendMessage(
			hWnd,
			WM_SETICON,
			ICON_BIG,
			(LPARAM)GetIconLarge(wsi->icon_id));
		SendMessage(
			hWnd,
			WM_SETICON,
			ICON_SMALL,
			(LPARAM)GetIconSmall(wsi->icon_id));
	}

	// set loop interval
	interval = wsi->interval;

	wgal_exit = FALSE;

	ShowWindow(hWnd, SW_SHOWNORMAL);

	return TRUE;
}

// sets up a WGAL instance
WGALAPI WGALBool wgalSetup(WGALStartupInfoPtr wsi)
{
	WGALStartupInfoEx wsix;
	WGALStartupInfoExPtr wsix_ptr = NULL;

	if(wsi)
	{
		// wsi isn't NULL
		wsix_ptr = &wsix;
		// clear struct
		memset(wsix_ptr, 0, sizeof(WGALStartupInfoEx));
		// copy data
		memcpy(wsix_ptr, wsi, sizeof(WGALStartupInfo));
	}

	return wgalSetupEx(wsix_ptr);
}

// deinitializes WGAL
WGALAPI WGALVoid wgalDeinit()
{
	if(!wgal_init)
		return;

	wgalFreeIconAll();
	wgalFreeResourceModuleAll();

	// just in case
	if(hWnd != NULL)
		DestroyWindow(hWnd);

	// unregister window class
	UnregisterClass(lpszWndClass, hDllInst);

	// release d3d interface
	d3d->Release();

	wgal_init = FALSE;
}

// WGAL message loop
WGALAPI WGALInt32 wgalStart()
{
	CHECK_INITIALIZED(wgal_init);	
	
	/* Startup */
	/*
	if(!wgalSetup(wsi))
		return WGAL_INIT_FAILED;
	*/

	if(!hWnd)
		return WGAL_NOT_READY;

	/* prepare timer */
	__int64 start, end, freq, update_ticks_pc;
	BOOL qpc_avail = QueryPerformanceFrequency((PLARGE_INTEGER)&freq);

	// check if QueryPerformanceCounter is available
	if(qpc_avail)
	{
		update_ticks_pc = (__int64)((double)interval * freq / 1000);
		QueryPerformanceCounter((PLARGE_INTEGER)&start);
	}
	else
	{
		update_ticks_pc = (__int64)(interval * 1000);
		start = timeGetTime();
		start *= 1000;
	}

	loop = TRUE;

    MSG msg;

	/* main loop */
    while(1)
    {
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_QUIT)
			{
				loop = FALSE;
                break;
			}

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
		else
		{
			if(qpc_avail)
			{
				QueryPerformanceCounter((PLARGE_INTEGER)&end);
			}
			else
			{
				end = timeGetTime();
				end *= 1000;
			}

			if(end - start >= update_ticks_pc)
			{
				WGALUInt32 cnt = 0;
				do
				{
					cnt++;					
					start += update_ticks_pc;
				}
				while(end - start >= update_ticks_pc);

				// update world
				update_proc(cnt);

				// exit flag is set
				if(wgal_exit)
				{
					DestroyWindow(hWnd);
					continue;
				}

				switch(d3ddev->TestCooperativeLevel())
				{
				case D3DERR_DEVICELOST:
					if(!device_lost)
					{
						device_lost = TRUE;
						// device lost.
						// call on-lost handlers
						OnDeviceLost();
					}
					continue;
				case D3DERR_DEVICENOTRESET:
					if(FAILED(d3ddev->Reset(&d3dpp)))
					{
						return WGAL_RESET_FAILED;
					}
					device_lost = FALSE;
					// device successfully reset;
					// perform reset for other devices
					OnDeviceReset();
				default:
					break;
				}

				// call user-supplied draw method
				draw_proc();
				
				if(auto_present)
				{
					// present to device
					wgalPresent();
				}
			}
		}
    }

	wgalGraphicsDeinit();

	main_ret = (int)msg.wParam;
	
	return WGAL_NO_ERROR;
}

// ends game loop
WGALAPI WGALVoid wgalExit()
{
	wgal_exit = TRUE;
}

// returns wParam value of WM_QUIT message
WGALAPI WGALInt32 wgalGetReturnValue()
{
	return main_ret;
}


WGALAPI WGALVoid wgalSetAutoPresent(WGALBool automatic)
{
	if(auto_present == automatic)
		return;
	
	auto_present = automatic;
}

WGALAPI WGALBool wgalIsAutoPresent()
{
	return auto_present;
}

// set game fullscreen or windowed
WGALAPI WGALBool wgalSetFullscreen(WGALBool fullscreen)
{
	// keep current state
	if(wgal_window != fullscreen)
		return TRUE;

	// toggle fullscreen
	return wgalToggleFullscreen();	
}

// returns whether the game is fullscreen
WGALAPI WGALBool wgalIsFullscreen()
{
	return !wgal_window;
}

// toggle fullscreen
WGALAPI WGALBool wgalToggleFullscreen()
{
	D3DPRESENT_PARAMETERS params;
	LONG_PTR oldStyle;
	//LONG_PTR oldExStyle;
	RECT rWindow;
	//RECT rRect = {0, 0, d3dpp.BackBufferWidth, d3dpp.BackBufferHeight};

	// copy parameters
	memcpy(&params, &d3dpp, sizeof(D3DPRESENT_PARAMETERS));

	// retrieve window position
	GetWindowRect(hWnd, &rWindow);

	/*
	AdjustWindowRectEx(&rRect, GetWindowStyle(!wgal_window), GetMenu(hWnd) != NULL, GetExWindowStyle(!wgal_window));
	rWindow.right = rWindow.left + rRect.right - rRect.left;
	rWindow.bottom = rWindow.top + rRect.bottom - rRect.top;
	*/

	// update window style
	oldStyle = SetWindowLongPtr(
		hWnd, GWL_STYLE,
		GetWindowStyle(wgal_window));
	// update extended window style
	/*
	oldExStyle = SetWindowLongPtr(
		hWnd, GWL_EXSTYLE,
		GetExWindowStyle(wgal_window));
		*/

	if(wgal_window)
	{
		// TODO: set fullscreen
		params.Windowed = FALSE;		
		params.BackBufferFormat = D3DFMT_X8R8G8B8;

		SetWindowPos(hWnd, HWND_TOPMOST,
			0, 0, 0, 0,
			SWP_SHOWWINDOW | SWP_NOSIZE);
	}
	else
	{
		// TODO: set windowed
		params.Windowed = TRUE;
		params.BackBufferFormat = D3DFMT_UNKNOWN;		

		/*
		SetWindowPos(hWnd, HWND_NOTOPMOST,
			0, 0, 0, 0,
			SWP_SHOWWINDOW | SWP_NOSIZE |
			SWP_DRAWFRAME | SWP_FRAMECHANGED);
			*/

		ShowWindow(hWnd, SW_HIDE);
	}

	OnDeviceLost();

	// try resetting the device
	if(FAILED(d3ddev->Reset(&params)))
	{
		// revert the changes
		SetWindowLongPtr(hWnd, GWL_STYLE, oldStyle);
		//SetWindowLongPtr(hWnd, GWL_EXSTYLE, oldExStyle);

		SetWindowPos(hWnd, wgal_window ? HWND_NOTOPMOST : HWND_TOPMOST,
			rWindow.left, rWindow.top,			
			0, 0,
			SWP_SHOWWINDOW | SWP_FRAMECHANGED |
			SWP_DRAWFRAME | SWP_NOSIZE);
		
		device_lost = TRUE;

		UpdateWindow(hWnd);
		// failed; return FALSE
		return FALSE;
	}

	OnDeviceReset();

	RECT rect = {0,0,d3dpp.BackBufferWidth,d3dpp.BackBufferHeight};
	if(!wgal_window)
	{
		// temporary workaround
		AdjustWindowRectEx(&rect, GetWindowStyle(FALSE), GetMenu(hWnd) != NULL, GetExWindowStyle(FALSE));

		SetWindowPos(hWnd, HWND_NOTOPMOST,
			0, 0,
			rect.right - rect.left,
			rect.bottom - rect.top,
			SWP_SHOWWINDOW | 
			SWP_DRAWFRAME | SWP_FRAMECHANGED);
	}

	
	UpdateWindow(hWnd);
	// start updating status variables
	wgal_window = !wgal_window;

	// update global parameters
	memcpy(&d3dpp, &params, sizeof(D3DPRESENT_PARAMETERS));

	// succeeded; return TRUE
	return TRUE;
}

WGALAPI WGALBool wgalIsActive()
{
	return GetForegroundWindow() == hWnd;
}

// sets whether to show system cursor
WGALAPI WGALVoid wgalShowCursor(WGALBool show)
{
	show_cursor = show;
	SendMessage(hWnd, WM_SETCURSOR, (WPARAM)hWnd, NULL);
}

// returns whether system cursor is visible
WGALAPI WGALBool wgalIsCursorVisible()
{
	return show_cursor;
}

// Window Local Storage
// stores window-specific data
struct WindowLocalStorage
{
	TRACKMOUSEEVENT tme;
	bool track_mouse;

	WindowLocalStorage()
	{
		init();
	}

	WindowLocalStorage(HWND hWnd)
	{
		init();
		
		tme.hwndTrack = hWnd;
	}

private:
	inline void init()
	{
		ZeroMemory(&tme, sizeof(TRACKMOUSEEVENT));
		track_mouse = false;

		tme.cbSize = sizeof(TRACKMOUSEEVENT);
		tme.dwFlags = TME_LEAVE;
	}
};

// WGAL window procedure
LRESULT CALLBACK wgalWndProc(HWND hWnd,
									UINT uMsg,
									WPARAM wParam,
									LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_CREATE:
		{
			// initialize window storage
			WindowLocalStorage *wndStor = new WindowLocalStorage(hWnd);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)wndStor);

			return 0;
		}
	case WM_DESTROY:
		// destroy window storage
		delete (void *)GetWindowLongPtr(hWnd, GWLP_USERDATA);

		if(loop)
			PostQuitMessage(0);
		::hWnd = NULL;

		return 0;		
	case WM_SETCURSOR:
		{
			WindowLocalStorage *wndStor =
				(WindowLocalStorage *)GetWindowLongPtr(hWnd, GWLP_USERDATA);

			if((wndStor->track_mouse || !wgal_window)
				&& !show_cursor)
			{
				SetCursor(NULL);
				return TRUE;
			}
			break;
		}
	case WM_MOUSEMOVE:
		{
			WindowLocalStorage *wndStor =
				(WindowLocalStorage *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
			if(!wndStor->track_mouse)
			{
				// Mouse entered
				//ShowCursor(FALSE);
				//d3ddev->ShowCursor(TRUE);

				if(TrackMouseEvent(&wndStor->tme))
					wndStor->track_mouse = true;
			}
			
			return 0;
		}
	case WM_MOUSELEAVE:
		{
			// mouse left
			//ShowCursor(TRUE);

			WindowLocalStorage *wndStor =
				(WindowLocalStorage *)GetWindowLongPtr(hWnd, GWLP_USERDATA);

			wndStor->track_mouse = false;

			return 0;
		}
	case WM_CLOSE:
		break;
	case WM_SYSCOMMAND:
		switch(wParam & 0xFFF0)
		{
		case SC_MAXIMIZE:
			if(maximize_proc)
			{
				if(maximize_proc())
				{
					return 0;
				}
			}
			break;
		case SC_MINIMIZE:
			if(minimize_proc)
			{
				if(minimize_proc())
				{
					return 0;
				}
			}
			break;
		case SC_CLOSE:
			if(close_proc)
			{
				if(close_proc())
				{
					return 0;
				}
			}
			break;
		}
		break;
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		default:
			if(command_proc)
			{
				if(command_proc())
				{
					return 0;
				}
			}
		}
		break;
	case WM_KEYDOWN:
	case WM_KEYUP:
		break;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

// get window style
DWORD GetWindowStyle(WGALBool fullscreen)
{
	DWORD dwStyle;

	if(fullscreen)
	{		
		dwStyle = WS_POPUP;
	}
	else
	{
		dwStyle = WS_OVERLAPPEDWINDOW;
	}

	// remove sizable border
	dwStyle &= ~WS_THICKFRAME;

	// minimize button
	switch(btn_minimize)
	{
	case WGAL_DISABLED:
		dwStyle &= ~WS_MINIMIZEBOX;
		break;
	case WGAL_ENABLED:
	case WGAL_DEFAULT: // enabled by default
	default:		
		break;
	}

	// maximize button
	switch(btn_maximize)
	{
	case WGAL_DISABLED:
		dwStyle &= ~WS_MAXIMIZEBOX;
		break;
	case WGAL_ENABLED:
		break;
	case WGAL_DEFAULT:
	default:
		// disabled on null procedure
		if(!maximize_proc)
			dwStyle &= ~WS_MAXIMIZEBOX;
		break;
	}

	return dwStyle;
}

// get extended window style
DWORD GetExWindowStyle(WGALBool fullscreen)
{

	DWORD dwExStyle = 0;
/*
	if(fullscreen)
	{
		dwExStyle = WS_EX_TOPMOST;
	}
	else
	{
		dwExStyle = WS_EX_APPWINDOW;
	}
	*/
	
	return dwExStyle;
}

// set close button state
void SetCloseButtonState(WGALInt32 state)
{
	if(!hWnd)
		return;

	if(btn_close == state)
		return;

	HMENU hSysMenu = GetSystemMenu(hWnd, FALSE);

	if(!hSysMenu)
		return;

	// close button
	switch(state)
	{
	case WGAL_DISABLED:
		//DeleteMenu(hSysMenu, 5, MF_BYPOSITION);
		//DeleteMenu(hSysMenu, SC_CLOSE, MF_BYCOMMAND);
		EnableMenuItem(hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
		break;
	case WGAL_ENABLED:
	case WGAL_DEFAULT: // enabled by default
	default:
		EnableMenuItem(hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);
		break;
	}

	btn_close = state;
}